Fork me on GitHub

Now on: joopl.Attribute

Available since 2.3.0

Index

1.0 What is an attribute?

Usually class definitions contain a class ctor, properties, methods and/or events, also known as class members. Class members define the information and behavior of a given class.

In some cases, classes require some descriptive information that may be useful by the consumers.

For example, a class may need to define that requires user authentication and the minimum security role to use its members is administrator.

How can an arbitrary class tell the environment "I will not work if the authenticated user is not an administrator"? The answer is *attributes.*

An attribute is an inherited class of Attribute which defines some metadata that can be identified by other pieces and it is added to the class definition during desing-time.

Finally, a class supports as many attributes as the code requires. The attributes parameters for the $def operator is an array of attributes.

2.0 How to implement and consume an attribute

The so-called I will not work if the authenticated user is not an administrator attribute may be implemented as a class called RequiresAuthenticationAttribute:

$namespace.using("joopl", "myNamespace", function(joopl, myNamespace) {
    myNamespace.declareClass("RequiresAuthenticationAttribute", {
        inherits: joopl.Attribute
    });
});

Later on, some class that may require authentication to work will apply the whole RequiresAuthenticationAttribute as follows:

$namespace.using("myNamespace", function(myNamespace) {
    myNamespace.declareClass("MyClass", {
        attributes: [new myNamespace.RequiresAuthenticationAttribute()]
    });
});

Finally, some other code which instantiate the MyClass class will inspect if the class requires authentication:

$namespace.using("myNamespace", function(myNamespace) {
    if(myNamespace.MyClass.type.hasAttribute(myNamespace.RequiresAuthenticationAttribute)) {
        // Do some stuff if MyClass has the whole attribute
    } else {
        throw Error("Sorry, this code will not execute classes if they do not require authentication...");
    }
});

2.1 Attributes with parameters

Sometimes using an attribute as is is not enough, because the attribute itself should contain data.

For example, some code may require some classes to define a default property. Person class may have FirstName, Surname and Nickname properties. Which one will be the one to display in some listing?

$namespace.using("joopl", "myNamespace", function(joopl, myNamespace) {
    myNamespace.declareClass("DefaultPropertyAttribute", {
        inherits: oopl.Attribute,
        ctor: function(args) {
            this._.defaultPropertyName = args.defaultPropertyName;
        },
        members: {
            get defaultPropertyName() { return this._.defaultPropertyName; }
        }
    });

    myNamespace.declareClass("Person", {
        attributes: [new myNamespace.DefaultPropertyAttribute("nickname")],
        ctor: function() {
            this._.firstName = null;
            this._.surname = null;
            this._.nickname = null;
        }
        members: {
            get firstName() { return this._.firstName; },
            set firstName(value) { this._.firstName = value; },

            get surname() { return this._.surname; },
            set surname(value) { this._.surname = value; },

            get nickname() { return this._.nickname; },
            set nickname(value) { this._.nickname = value; }
        }
    });
});

Now, some code consumes instances of Person and creates some HTML listing using standard DOM and the display name for the whole person will be taken from the DefaultPropertyValueAttribute:

$namespace.using("myNamespace", function(myNamespace) {

    // The first step is creating a regular instance of Person
    var person = new myNamespace.Person();
    person.firstName = "Matias";
    person.surname = "Fidemraizer";
    person.nickname = "mfidemraizer";

    // Secondly, this is checking if the Person class has the whole attribute
    if(Person.type.hasAttribute(myNamespace.DefaultPropertyAttribute)) {
        // Yes, it has the attribute!
        //
        // Then, the attribute instance is retrieved from the type information
        var defaultProperty = Person.type.getAttribute(myNamespace.DefaultPropertyAttribute);

        // Once the attribute is retrieved, the code can access the "defaultPropertyName" instance property
        // of the DefaultPropertyAttribute
        var defaultPropertyName = defaultProperty.defaultPropertyName;

        // Since any object is also an associative array (this is plain JavaScript!), 
        // the default property can be retrieved by using the "defaultPropertyName" variable
        // as key of the array
        var defaultPropertyValue = person[defaultPropertyName];

        // Finally, this is creating a paragraph containing the defaultPropertyValue. In this case, 
        // it will be "mfidemraizer", because the Person class has the DefaultPropertyAttribute set to "nickname"!
        var p = document.createElement("p");
        p.appendChild(document.createTextNode(defaultPropertyValue));
        document.body.appendChild(p);
    }
});